
#include "Arduino.h"
#include "Tone.h"
#include "pinmap.h"

#define MAX_FREQ 0xFFFFFFFF

typedef struct
{
    PinName pin;
    int32_t count;
} timerPinInfo_t;

static timerPinInfo_t TimerTone_pinInfo = {NC, 0};
static HardwareTimer *TimerTone = NULL;

static void tonePeriodElapsedCallback()
{
    GPIO_TypeDef *GPIOx = REF_GPIO_PORT(TimerTone_pinInfo.pin);
    uint32_t GPIO_Pin = REF_GPIO_PIN(TimerTone_pinInfo.pin);

    if (GPIOx != NULL)
    {
        if (TimerTone_pinInfo.count != 0)
        {
            if (TimerTone_pinInfo.count > 0)
                TimerTone_pinInfo.count--;

            GPIOx->OUTDR ^= GPIO_Pin;
        }
        else
        {
            GPIO_WriteBit(GPIOx, GPIO_Pin, Bit_RESET);
        }
    }
}

static inline void timerTonePinDeinit()
{
    if (TimerTone != NULL)
    {
        TimerTone->timerHandleDeinit();
    }
    if (TimerTone_pinInfo.pin != NC)
    {
        pin_function(TimerTone_pinInfo.pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));
        TimerTone_pinInfo.pin = NC;
    }
}

static inline void timerTonePinInit(PinName p, uint32_t frequency, uint32_t duration)
{
    uint32_t timFreq = 2 * frequency;

    if (frequency <= MAX_FREQ)
    {
        if (frequency == 0)
        {
            if (TimerTone != NULL)
                TimerTone->pause();
        }
        else
        {
            TimerTone_pinInfo.pin = p;

            if (duration > 0)
            {
                TimerTone_pinInfo.count = ((timFreq * duration) / 1000);
            }
            else
            {
                TimerTone_pinInfo.count = -1;
            }
        }

        pin_function(TimerTone_pinInfo.pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0));

        TimerTone->setMode(1, TIMER_OUTPUT_COMPARE, NC);
        TimerTone->setOverflow(timFreq, HERTZ_FORMAT);
        TimerTone->attachInterrupt(tonePeriodElapsedCallback);
        TimerTone->resume();
    }
}

void tone(uint8_t _pin, unsigned int frequency, unsigned long duration)
{
    PinName p = digitalPinToPinName(_pin);

    if (TimerTone == NULL)
        TimerTone = new HardwareTimer(TIMER_TONE);

    if (p != NC)
    {
        if ((TimerTone_pinInfo.pin == NC) || TimerTone_pinInfo.pin == p)
        {
            timerTonePinInit(p, frequency, duration);
        }
    }
}

void noTone(uint8_t _pin, bool destruct)
{
    PinName p = digitalPinToPinName(_pin);

    if ((p != NC) && (TimerTone_pinInfo.pin == p) && (TimerTone != NULL))
    {
        if (destruct)
        {
            timerTonePinDeinit();
            delete (TimerTone);
            TimerTone = NULL;
        }
        else
        {
            TimerTone->pause();
        }
    }
}
